package main;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import message.Message;

import com.chaosinmotion.asn1.BerInputStream;
import com.chaosinmotion.asn1.BerOutputStream;

public class Server {
	private ServerSocket server = null;
	private int port = 5000;
	private boolean printHex = false;
	private ExecutorService scheduler;

	public Server(String[] args) {
		try {
			analysePara(args);
			server = new ServerSocket(port);
			System.out.println("TCP Server started on port: " + port);
			scheduler = Executors.newCachedThreadPool();
			ClientHandler handler = null;
			Socket client = null;
			int clientNum = 0;
			while (true) {
				client = server.accept();
				System.out.println("Connected with client" + (++clientNum)
						+ "(" + client.getInetAddress() + ":"
						+ client.getPort() + ")");
				handler = new ClientHandler(client, clientNum);
				scheduler.execute(handler);
			}
		} catch (IOException e) {
			System.out.println("Server broken down");
		}
	}

	private void analysePara(String[] args) {
		if (args.length == 1 && args[0].equals("-d"))
			printHex = true;
	}

	public static void main(String[] args) throws Exception {
		new Server(args);
	}

	class ClientHandler implements Runnable {
		private InputStream input = null;
		private OutputStream output = null;
		private String clientName = null;
		private Socket client = null;
		
		ClientHandler(Socket client, int clientNum) {
			this.client = client;
			this.clientName = "Client" + clientNum;
			try {
				input = client.getInputStream();
				output = client.getOutputStream();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		public void run() {
			try {
				while (true) {
					// receive a number and increment it by 1
					Message recvMsg = receiveMessage();
					long number = recvMsg.num.getValue() + 1;

					// generate a random string in form
					String append = genRandomString();

					Message sentMsg = new Message();
					sentMsg.num.setValue(number);
					sentMsg.str.setValue(append.getBytes());

					// send the generated result to the client
					sendMessage(sentMsg);

					// receive the modified result from the client
					Message retMsg = receiveMessage();

					// verify the result
					String returnString = new String(retMsg.str.getValue());
					if (returnString.equals(convert(append)))
						retMsg.str.setValue("OK".getBytes());
					else
						retMsg.str.setValue("FAIL".getBytes());

					sendMessage(retMsg);
				}
			} catch (NumberFormatException ne) {
				System.out.println(clientName + " hasn't sent an integer");
			} catch (IOException e) {
				System.out.println("Disconnected with " + clientName);
			} finally {
				try {
					if (input != null)
						input.close();
					if (output != null)
						output.close();
					if (client != null)
						client.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}

		private String convert(String append) {
			char[] chars = append.toCharArray();
			for (int i = 0; i < chars.length; i++) {
				if (chars[i] >= 'a' && chars[i] <= 'z')
					chars[i] -= 32;
				else if (chars[i] >= 'A' && chars[i] <= 'Z')
					chars[i] += 32;
			}
			return new String(chars);
		}

		public Message receiveMessage() throws IOException {
			byte[] b = new byte[1024];
			input.read(b);
			ByteArrayInputStream input = new ByteArrayInputStream(b);
			BerInputStream in = new BerInputStream(input);
			Message msg = new Message();
			msg.decode(in);
			System.out.println(clientName + " -> Server: " + msg.num.getValue()
					+ " " + new String(msg.str.getValue()));
			return msg;
		}

		public void sendMessage(Message msg) {
			System.out
					.println("Server" + " -> " + clientName + ": "
							+ msg.num.getValue() + " "
							+ new String(msg.str.getValue()));
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			BerOutputStream out = new BerOutputStream(baos);
			try {
				msg.encode(out);
				if (printHex) {
					printHex(baos.toByteArray());
				}
				output.write(baos.toByteArray());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		private String genRandomString() {
			Random random = new Random();
			int numOfChars = random.nextInt(11) + 10;
			char[] numbersAndLetters = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
					.toCharArray();
			char[] randBuffer = new char[numOfChars];
			for (int i = 0; i < numOfChars; i++)
				randBuffer[i] = numbersAndLetters[random.nextInt(62)];
			return new String(randBuffer);
		}

		public void printHex(byte[] coded) {
			String hexDigits = "0123456789ABCDEF";
			for (int i = 0; i < coded.length; i++) {
				int c = coded[i];
				if (c < 0)
					c += 256;
				int hex1 = c & 0xF;
				int hex2 = c >> 4;
				System.out.print("0x" + hexDigits.substring(hex2, hex2 + 1));
				System.out.print(hexDigits.substring(hex1, hex1 + 1) + " ");
			}
			System.out.println();
		}
	}
}
